package org.hepeng.workx.mybatis.session;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.visitor.SchemaStatVisitor;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.CachingExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ReuseExecutor;
import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.transaction.Transaction;
import org.hepeng.workx.extension.DefaultXImpl;
import org.hepeng.workx.extension.XLoader;
import org.hepeng.workx.mybatis.MybatisConstant;
import org.hepeng.workx.mybatis.datasource.DataSourceRegistry;
import org.hepeng.workx.mybatis.datasource.DataSourceRouteObjectProcessor;
import org.hepeng.workx.mybatis.datasource.SelectableDataSourceFactory;
import org.hepeng.workx.mybatis.event.consumer.AttributeSqlSessionWrapObjectProcessor;
import org.hepeng.workx.mybatis.event.consumer.EventConsumerBindObjectProcessor;
import org.hepeng.workx.mybatis.event.listener.DefaultExecuteEventListener;
import org.hepeng.workx.mybatis.event.listener.ExecuteEventListener;
import org.hepeng.workx.mybatis.event.listener.ExecuteEventListenerRegistry;
import org.hepeng.workx.mybatis.event.publisher.ExecuteEventPublisher;
import org.hepeng.workx.mybatis.executor.EventPublishExecutorProxy;
import org.hepeng.workx.mybatis.util.WorkXMybatisEnvironment;
import org.hepeng.workx.util.CompositeObjectProcessor;
import org.hepeng.workx.util.ObjectProcessor;
import org.hepeng.workx.util.proxy.Invoker;
import org.hepeng.workx.util.proxy.ProxyFactory;
import org.joor.Reflect;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;

/**
 * @author he peng
 */

@DefaultXImpl
public class ConfigurationX extends Configuration {

    private static final ExecuteEventPublisher EXECUTE_EVENT_PUBLISHER = XLoader.getXLoader(ExecuteEventPublisher.class , MybatisConstant.MYBATIS_X_POINT_DIRECTORY).getX();
    private static ProxyFactory PROXY_FACTORY = XLoader.getXLoader(ProxyFactory.class).getX();
    protected DataSourceRegistry dataSourceRegistry = new DataSourceRegistry();
    protected SchemaStatVisitor schemaStatVisitor;
    protected ExecuteEventListenerRegistry executeEventListenerRegistry = new ExecuteEventListenerRegistry();

    private synchronized void initialize() {
        Reflect thisReflect = Reflect.on(this);
        List<Class<?>> constructorArgTypes = new ArrayList<>();
        constructorArgTypes.add(Configuration.class);
        constructorArgTypes.add(ObjectProcessor.class);
        List<Object> constructorArgs = new ArrayList<>();
        constructorArgs.add(this);
        CompositeObjectProcessor objectProcessor = new CompositeObjectProcessor();
        if (WorkXMybatisEnvironment.isEnableDataSourceRoute(variables)) {
            objectProcessor.addObjectProcessor(new DataSourceRouteObjectProcessor());
            super.typeAliasRegistry.registerAlias("SELECTABLE" , SelectableDataSourceFactory.class);
        }

        if (WorkXMybatisEnvironment.isEnablePublishEvent(variables)) {
            objectProcessor.addObjectProcessor(
                    new EventConsumerBindObjectProcessor(executeEventListenerRegistry));
            objectProcessor.addObjectProcessor(new AttributeSqlSessionWrapObjectProcessor());
        }

        constructorArgs.add(objectProcessor);
        MapperRegistry mapperRegistry = XLoader.getXLoader(
                MapperRegistry.class ,
                MybatisConstant.MYBATIS_X_POINT_DIRECTORY)
                .getX(constructorArgTypes, constructorArgs);
        thisReflect.set("mapperRegistry" , mapperRegistry);
    }

    protected void addExecuteEventListener(Set<ExecuteEventListener> executeEventListeners) {
        this.executeEventListenerRegistry.addExecuteEventListener(executeEventListeners);
    }


    @Override
    public void setVariables(Properties variables) {
        super.setVariables(variables);
        initialize();

        if (Objects.isNull(variables)) {
            return;
        }
        ExecuteEventListener listener = new DefaultExecuteEventListener(true , WorkXMybatisEnvironment.getEventConsumerPackages(variables));
        executeEventListenerRegistry.addExecuteEventListener(listener);
        schemaStatVisitor = SQLUtils.createSchemaStatVisitor(WorkXMybatisEnvironment.getDialect(variables));
    }

    @Override
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        if (! WorkXMybatisEnvironment.isEnablePublishEvent(variables)) {
            return super.newExecutor(transaction , executorType);
        }

        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }

        List<Class<?>> argTypes = new ArrayList<>();
        List<Object> args = new ArrayList<>();
        argTypes.add(Configuration.class);
        argTypes.add(Transaction.class);
        args.add(this);
        args.add(transaction);

        List<Invoker> invokers = new LinkedList<>();
        invokers.add(new EventPublishExecutorProxy(EXECUTE_EVENT_PUBLISHER));
        Object proxy = PROXY_FACTORY.createProxy(executor.getClass() , null , argTypes, args , invokers , null);
        if (cacheEnabled) {
            proxy = new CachingExecutor((Executor) proxy);
        }
        proxy = interceptorChain.pluginAll(proxy);

        return (Executor) proxy;
    }

}
